/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
package org.unidata.mdm.meta.util;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.validation.constraints.NotNull;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.unidata.mdm.meta.dto.FullModelDTO;
import org.unidata.mdm.meta.exception.MetaExceptionIds;
import org.unidata.mdm.meta.type.input.meta.MetaAction;
import org.unidata.mdm.meta.type.input.meta.MetaExistence;
import org.unidata.mdm.meta.type.input.meta.MetaGraph;
import org.unidata.mdm.meta.type.input.meta.MetaMessage;
import org.unidata.mdm.meta.type.input.meta.MetaPropKey;
import org.unidata.mdm.meta.type.input.meta.MetaStatus;
import org.unidata.mdm.meta.type.input.meta.MetaType;
import org.unidata.mdm.meta.type.input.meta.MetaVertex;
import org.unidata.mdm.meta.type.model.DataModel;
import org.unidata.mdm.meta.type.model.attributes.ArrayMetaModelAttribute;
import org.unidata.mdm.meta.type.model.attributes.AttributeMeasurementSettings;
import org.unidata.mdm.meta.type.model.attributes.ComplexMetaModelAttribute;
import org.unidata.mdm.meta.type.model.attributes.SimpleMetaModelAttribute;
import org.unidata.mdm.meta.type.model.entities.EntitiesGroup;
import org.unidata.mdm.meta.type.model.entities.Entity;
import org.unidata.mdm.meta.type.model.entities.LookupEntity;
import org.unidata.mdm.meta.type.model.entities.NestedEntity;
import org.unidata.mdm.meta.type.model.entities.Relation;
import org.unidata.mdm.meta.type.model.measurement.MeasurementCategory;
import org.unidata.mdm.meta.type.model.merge.MergeAttribute;
import org.unidata.mdm.meta.type.model.merge.MergeSettings;
import org.unidata.mdm.meta.type.model.sourcesystem.SourceSystem;
import org.unidata.mdm.system.util.TextUtils;

/**
 * todo: the logic has been ported from version 5.2, has been refactored, but still needs to be optimized and refactored
 *
 * @author maria.chistyakova
 */
@Component
public class MetaModelGraphComponent {

    /**
     * The Constant PATH_DELIMETER.
     */
    private static final String PATH_DELIMETER = ".";

    /**
     * Enrich.
     *
     * @param fullModelDTOToCacheFromZip the to process
     * @param metaGraphToResponse the metaGraphToResponse
     * @param defaultStatus the default status
     * @param metaTypes the meta types
     */
    public void enrich(
            FullModelDTO fullModelDTOToCacheFromZip,
            MetaGraph metaGraphToResponse,
            MetaExistence defaultStatus,
            @NotNull MetaType[] metaTypes
    ) {

        Set<MetaType> typesToProcess = Arrays.stream(metaTypes).filter(Objects::nonNull).collect(Collectors.toSet());

        DataModel fullModelDTOToCacheFromZipModel = fullModelDTOToCacheFromZip.getModel();

        if (fullModelDTOToCacheFromZipModel == null) {
            return;
        }

        processGroups(fullModelDTOToCacheFromZipModel, metaGraphToResponse, defaultStatus, typesToProcess);


        processNestedEntities(metaGraphToResponse, defaultStatus, typesToProcess, fullModelDTOToCacheFromZipModel.getNestedEntities());

        // lookup entities
        processLookupEntities(metaGraphToResponse, defaultStatus, typesToProcess, fullModelDTOToCacheFromZipModel.getLookupEntities());

        processEntity(metaGraphToResponse, defaultStatus, typesToProcess, fullModelDTOToCacheFromZipModel.getEntities());
        // enumerations

        processEnumerations(fullModelDTOToCacheFromZipModel, metaGraphToResponse, defaultStatus, typesToProcess);

        // measurements
        processMeasurements(fullModelDTOToCacheFromZip,
                metaGraphToResponse, defaultStatus, typesToProcess
        );

        // relations
        processRelations(metaGraphToResponse, defaultStatus, typesToProcess, fullModelDTOToCacheFromZipModel.getRelations());

        // source systems
        sourceSystemsProcess(fullModelDTOToCacheFromZipModel, metaGraphToResponse, defaultStatus, typesToProcess);


        checkDuplicates(metaGraphToResponse);
    }

    private void processMeasurements(FullModelDTO toProcess, MetaGraph result,
                                     MetaExistence defaultStatus, Set<MetaType> typesToProcess) {
        if (toProcess.getMeasurementValues() == null || !typesToProcess.contains(MetaType.MEASURE)) {
            return;
        }

        toProcess.getMeasurementValues()
                .getValues()
                .forEach(value -> {
                    MetaVertex vertex = new MetaVertex.MetaVertexBuilder()
                            .id(value.getName())
                            .displayName(value.getDisplayName())
                                    .type(MetaType.MEASURE)
                                    .status(defaultStatus)
                                    .action(calculatingActionFromStatus(defaultStatus))
                                    .build();

                            addVertex(result, vertex);
                        }

                );
    }

    private void processGroups(DataModel model, MetaGraph result, MetaExistence defaultStatus, Set<MetaType> typesToProcess) {
        if (typesToProcess.contains(MetaType.GROUPS) && model.getEntitiesGroup() != null) {
            addEntitiesGroups(model.getEntitiesGroup(), "", null, result, defaultStatus);
        }
    }

    @Deprecated(forRemoval = true)
    private void processMeasurements(MetaGraph result, MetaExistence defaultStatus, Set<MetaType> typesToProcess,
                                     List<MeasurementCategory> mvds) {
        if (!typesToProcess.contains(MetaType.MEASURE)) {
            return;
        }
        for (MeasurementCategory value : mvds) {
            addVertex(
                    result,
                    new MetaVertex.MetaVertexBuilder()
                            .id(value.getName())
                            .displayName(value.getDisplayName())
                            .type(MetaType.MEASURE)
                            .status(defaultStatus)
                            .action(calculatingActionFromStatus(defaultStatus))
                            .build()
            );
        }
    }

    private void processEnumerations(DataModel model, MetaGraph result, MetaExistence defaultStatus,
                                     Set<MetaType> typesToProcess) {
        if (!typesToProcess.contains(MetaType.ENUM)) {
            return;
        }
//        model.getEnumerations().forEach(
//                value ->
//                        addVertex(result,
//                                new MetaVertex.MetaVertexBuilder()
//                                        .id(value.getName())
//                                        .displayName(value.getDisplayName())
//                                        .type(MetaType.ENUM)
//                                        .status(defaultStatus)
//                                        .action(calculatingActionFromStatus(defaultStatus))
//                                        .build()
//                        )
//        );
    }

    private void processEntity(MetaGraph result, MetaExistence defaultStatus, Set<MetaType> typesToProcess,
                               List<Entity> entities) {
        if (!typesToProcess.contains(MetaType.ENTITY) || CollectionUtils.isEmpty(entities)) {
            return;
        }
        // entities
        for (Entity value : entities) {
            addVertex(result,
                    new MetaVertex.MetaVertexBuilder()
                            .id(value.getName())
                            .displayName(value.getDisplayName())
                            .type(MetaType.ENTITY)
                            .status(defaultStatus)
                            .action(calculatingActionFromStatus(defaultStatus))
                            .build()
            );
        }
        processEntities(entities, result, typesToProcess);
    }

    private void processNestedEntities(MetaGraph result, MetaExistence defaultStatus, Set<MetaType> typesToProcess,
                                       List<NestedEntity> ness) {
        if (!typesToProcess.contains(MetaType.NESTED_ENTITY) || CollectionUtils.isEmpty(ness)) {
            return;
        }

        for (NestedEntity value : ness) {
            addVertex(
                    result,
                    new MetaVertex.MetaVertexBuilder()
                            .id(value.getName())
                            .displayName(value.getDisplayName())
                            .type(MetaType.NESTED_ENTITY)
                            .status(defaultStatus)
                            .action(calculatingActionFromStatus(defaultStatus))
                            .build()
            );
        }

        if (typesToProcess.contains(MetaType.NESTED_ENTITY)) {
            for (NestedEntity nes : ness) {
                processNestedEntity(nes, result, defaultStatus, typesToProcess);
            }
        }
    }

    private void processLookupEntities(MetaGraph result, MetaExistence defaultStatus, Set<MetaType> typesToProcess,
                                       List<LookupEntity> lookupEntities) {

        if (!typesToProcess.contains(MetaType.LOOKUP) || CollectionUtils.isEmpty(lookupEntities)) {
            return;
        }

        for (LookupEntity value : lookupEntities) {
            addVertex(
                    result,
                    new MetaVertex.MetaVertexBuilder()
                            .id(value.getName())
                            .displayName(value.getDisplayName())
                            .type(MetaType.LOOKUP)
                            .status(defaultStatus)
                            .action(calculatingActionFromStatus(defaultStatus))
                            .build()
            );

        }
        processLookupEntities(lookupEntities, result, typesToProcess);
    }

    private void sourceSystemsProcess(DataModel model, MetaGraph result, MetaExistence defaultStatus,
                                      Set<MetaType> typesToProcess) {
//        if (typesToProcess.contains(MetaType.SOURCE_SYSTEM) && !CollectionUtils.isEmpty(model.getSourceSystems())) {
//            for (SourceSystem s : model.getSourceSystems()) {
//                addVertex(result,
//                        new MetaVertex.MetaVertexBuilder()
//                                .id(s.getName())
//                                .displayName(s.getName())
//                                .type(MetaType.SOURCE_SYSTEM)
//                                .status(defaultStatus)
//                                .action(calculatingActionFromStatus(defaultStatus))
//                                .build()
//                );
//            }
//        }
    }

    /**
     * Adds the entities groups.
     *
     * @param egs the egs
     * @param path the path
     * @param root the root
     * @param result the result
     * @param defaultStatus the default status
     */
    private void addEntitiesGroups(EntitiesGroup egs, String path, MetaVertex root,
                                   MetaGraph result, MetaExistence defaultStatus) {
        path = StringUtils.isEmpty(path) ? egs.getName() : String.join(PATH_DELIMETER, path, egs.getName());

        MetaVertex eVertex =
                new MetaVertex.MetaVertexBuilder()
                        .id(path)
                        .displayName(egs.getDisplayName())
                        .type(MetaType.GROUPS)
                        .status(defaultStatus)
                        .action(calculatingActionFromStatus(defaultStatus))
                        .build();

        addVertex(result, eVertex);

        if (root != null) {
            result.addEdge(eVertex, root);
        }

        List<EntitiesGroup> defs = egs.getInnerGroups();
        for (EntitiesGroup entitiesGroup : defs) {
            addEntitiesGroups(entitiesGroup, path, eVertex, result, defaultStatus);
        }

    }


    private void processRelations(MetaGraph result, MetaExistence defaultStatus, Set<MetaType> typesToProcess,
                                  List<Relation> relations) {
        if (!typesToProcess.contains(MetaType.RELATION) || CollectionUtils.isEmpty(relations)) {
            return;
        }


        for (Relation value : relations) {

            MetaVertex vertex = new MetaVertex.MetaVertexBuilder()
                    .id(value.getName())
                    .displayName(value.getDisplayName())
                    .type(MetaType.RELATION)
                    .status(defaultStatus)
                    .action(calculatingActionFromStatus(defaultStatus))
                    .build();


            Map<MetaPropKey, String> customProps = new HashMap<>();
            customProps.put(MetaPropKey.FROM, value.getFromEntity());
            customProps.put(MetaPropKey.TO, value.getToEntity());
            customProps.put(MetaPropKey.REL_TYPE, value.getRelType().value());
            vertex.setCustomProps(customProps);
            addVertex(result, vertex);
        }

        for (Relation rel : relations) {
            processRelation(rel, result);
        }

    }

    /**
     * Process relation.
     *
     * @param rel the rel
     * @param result the result
     */
    private void processRelation(Relation rel, MetaGraph result) {
        MetaVertex eVertex = new MetaVertex(rel.getName(), MetaType.RELATION);
        if (!result.containsVertex(new MetaVertex(rel.getFromEntity(), MetaType.ENTITY))) {
            result.addVertex(
                    new MetaVertex.MetaVertexBuilder()
                            .id(rel.getFromEntity())
                            .displayName(rel.getFromEntity())
                            .type(MetaType.ENTITY)
                            .status(MetaExistence.NOT_FOUND)
                            .action(MetaAction.NONE)
                            .messages(getNotFoundErrorMessage(rel.getFromEntity(), MetaType.ENTITY))
                            .build()
            );
        }

        result.addEdge(eVertex, new MetaVertex(rel.getFromEntity(), MetaType.ENTITY));

        if (!result.containsVertex(new MetaVertex(rel.getToEntity(), MetaType.ENTITY))) {
            result.addVertex(
                    new MetaVertex.MetaVertexBuilder()
                            .id(rel.getToEntity())
                            .displayName(rel.getToEntity())
                            .type(MetaType.ENTITY)
                            .status(MetaExistence.NOT_FOUND)
                            .action(MetaAction.NONE)
                            .messages(getNotFoundErrorMessage(rel.getToEntity(), MetaType.ENTITY))
                            .build()
            );
        }
        result.addEdge(eVertex, new MetaVertex(rel.getToEntity(), MetaType.ENTITY));

    }

    private MetaMessage getNotFoundErrorMessage(String entityName, MetaType entityType) {
        return createErrorMessage(
                TextUtils.getText(
                        MetaExceptionIds.EX_META_IMPORT_MODEL_EL_NOT_FOUND.code(),
                        entityName,
                        entityType
                ));
    }


    /**
     * Process nested entitie.
     *
     * @param nes the nes
     * @param result the result
     * @param defaultStatus the default status
     * @param typesToProcess the types to process
     */
    private void processNestedEntity(NestedEntity nes, MetaGraph result, MetaExistence defaultStatus,
                                     Set<MetaType> typesToProcess) {

        MetaVertex eVertex =
                new MetaVertex.MetaVertexBuilder()
                        .id(nes.getName())
                        .displayName(nes.getDisplayName())
                        .type(MetaType.NESTED_ENTITY)
                        .status(defaultStatus)
                        .action(calculatingActionFromStatus(defaultStatus))
                        .messages(getNotFoundErrorMessage(nes.getDisplayName(), MetaType.NESTED_ENTITY))
                        .build();
        processComplexAttributes(result, eVertex, nes.getComplexAttribute(), typesToProcess);
        if (typesToProcess.contains(MetaType.SOURCE_SYSTEM)) {
            processMergeSettings(result, eVertex, nes.getMergeSettings());
        }


        processSimpleAttributes(result, eVertex, nes.getSimpleAttribute(), typesToProcess);
        List<ArrayMetaModelAttribute> aas = nes.getArrayAttribute();
        processAAs(result, eVertex, aas, typesToProcess);
    }


    /**
     * Process entities.
     *
     * @param es the es
     * @param result the result
     * @param typesToProcess the types to process
     */
    private void processEntities(List<Entity> es, MetaGraph result, Set<MetaType> typesToProcess) {
        for (Entity e : es) {
            MetaVertex eVertex = new MetaVertex(e.getName(), MetaType.ENTITY);


            List<SimpleMetaModelAttribute> sas = e.getSimpleAttribute();
            processSimpleAttributes(result, eVertex, sas, typesToProcess);
            List<ArrayMetaModelAttribute> aas = e.getArrayAttribute();
            processAAs(result, eVertex, aas, typesToProcess);
            if (typesToProcess.contains(MetaType.SOURCE_SYSTEM)) {
                MergeSettings mergeSettings = e.getMergeSettings();
                processMergeSettings(result, eVertex, mergeSettings);
            }
            List<ComplexMetaModelAttribute> complexAttributes = e.getComplexAttribute();
            processComplexAttributes(result, eVertex, complexAttributes, typesToProcess);
            if (typesToProcess.contains(MetaType.GROUPS)) {
                processGroupName(result, eVertex, e.getGroupName());
            }
        }
    }

    /**
     * Process complex attributes.
     *
     * @param result the result
     * @param eVertex the e vertex
     * @param complexAttributes the complex attributes
     * @param typesToProcess the types to process
     */
    private void processComplexAttributes(MetaGraph result, MetaVertex eVertex,
                                          List<ComplexMetaModelAttribute> complexAttributes, Set<MetaType> typesToProcess) {
        if (complexAttributes == null || !typesToProcess.contains(MetaType.NESTED_ENTITY)) {
            return;
        }
        for (ComplexMetaModelAttribute nes : complexAttributes) {
            if (!result.containsVertex(new MetaVertex(nes.getNestedEntityName(), MetaType.NESTED_ENTITY))) {
                result.addVertex(
                        new MetaVertex.MetaVertexBuilder()
                                .id(nes.getNestedEntityName())
                                .displayName(nes.getNestedEntityName())
                                .type(MetaType.NESTED_ENTITY)
                                .status(MetaExistence.NOT_FOUND)
                                .action(MetaAction.NONE)
                                .messages(getNotFoundErrorMessage(nes.getDisplayName(), MetaType.NESTED_ENTITY))
                                .build()
                );

            }
            result.addEdge(eVertex, new MetaVertex(nes.getNestedEntityName(), MetaType.NESTED_ENTITY));

        }

    }

    /**
     * Process merge settings.
     *
     * @param result the result
     * @param eVertex the e vertex
     * @param mergeSettings the merge settings
     */
    private void processMergeSettings(MetaGraph result, MetaVertex eVertex, MergeSettings mergeSettings) {
        if (mergeSettings == null) {
            return;
        }

        if (mergeSettings.getBvrSettings() == null) {
            return;
        }

        Set<String> sourceSystems = new HashSet<>();

        if (mergeSettings.getBvrSettings() != null
                && mergeSettings.getBvrSettings().getSourceSystemsConfigs() != null) {

            List<String> mergeSettingNames = mergeSettings.getBvrSettings().getSourceSystemsConfigs().stream()
                    .map(SourceSystem::getName)
                    .collect(Collectors.toList());

            sourceSystems.addAll(mergeSettingNames);

        }

        if (mergeSettings.getBvtSettings() != null && mergeSettings.getBvtSettings().getAttributes() != null) {

            List<String> mergeSettingNames = mergeSettings.getBvtSettings().getAttributes().stream()
                    .map(MergeAttribute::getSourceSystemsConfigs)
                    .flatMap(Collection::stream)
                    .map(SourceSystem::getName)
                    .collect(Collectors.toList());

            sourceSystems.addAll(mergeSettingNames);

        }

        sourceSystems
                .stream()
                .filter(nes -> !StringUtils.isEmpty(nes))
                .forEach(nes -> {
                    if (!result.containsVertex(new MetaVertex(nes, MetaType.SOURCE_SYSTEM))) {
                        result.addVertex(
                                new MetaVertex.MetaVertexBuilder()
                                        .id(nes)
                                        .displayName(nes)
                                        .type(MetaType.SOURCE_SYSTEM)
                                        .status(MetaExistence.NOT_FOUND)
                                        .action(MetaAction.NONE)
                                        .messages(getNotFoundErrorMessage(nes, MetaType.SOURCE_SYSTEM))
                                        .build()
                        );
                    }
                    MetaVertex targetVertex = new MetaVertex(nes, MetaType.SOURCE_SYSTEM);
                    if (!result.containsEdge(eVertex, targetVertex)) {
                        result.addEdge(eVertex, targetVertex);
                    }
                });

    }

    /**
     * Process S as.
     *
     * @param result the result
     * @param sourceVertex the source vertex
     * @param sas the sas
     * @param typesToProcess the types to process
     */
    private void processSimpleAttributes(MetaGraph result, MetaVertex sourceVertex,
                                         List<SimpleMetaModelAttribute> sas,
                                         Set<MetaType> typesToProcess) {
        if (sas == null) {
            return;
        }

        for (SimpleMetaModelAttribute simpleAttribute : sas) {

            processMeasureSimpleAttribute(result, sourceVertex, typesToProcess, simpleAttribute);
            processEnumSimpleAttribute(result, sourceVertex, typesToProcess, simpleAttribute);
            processSimpleLookupAttribute(result, sourceVertex, typesToProcess, simpleAttribute.getLookupEntityType());
        }
    }

    private void processMeasureSimpleAttribute(
            MetaGraph result, MetaVertex sourceVertex,
            Set<MetaType> typesToProcess, SimpleMetaModelAttribute simpleAttribute
    ) {
        if (!typesToProcess.contains(MetaType.MEASURE)) {
            return;
        }
        AttributeMeasurementSettings amsd = simpleAttribute.getMeasureSettings();

        if (amsd == null || StringUtils.isEmpty(amsd.getCategoryId())) {
            return;
        }

        if (!result.containsVertex(new MetaVertex(amsd.getCategoryId(), MetaType.MEASURE))) {
            MetaMessage errorMessage = getNotFoundErrorMessage(amsd.getCategoryId(), MetaType.MEASURE);
            MetaVertex metaVertex = new MetaVertex.MetaVertexBuilder()
                    .id(amsd.getCategoryId())
                    .displayName(amsd.getCategoryId())
                    .type(MetaType.MEASURE)
                    .status(MetaExistence.NOT_FOUND)
                    .action(MetaAction.NONE)
                    .messages(errorMessage)
                    .build();
            result.addVertex(metaVertex);
        }
        MetaVertex targetVertex = new MetaVertex(amsd.getCategoryId(), MetaType.MEASURE);
        if (!result.containsEdge(sourceVertex, targetVertex)) {
            result.addEdge(sourceVertex, targetVertex);
        }
    }

    private void processEnumSimpleAttribute(MetaGraph result, MetaVertex sourceVertex, Set<MetaType> typesToProcess,
                                            SimpleMetaModelAttribute simpleAttribute) {
        if (typesToProcess.contains(MetaType.ENUM)) {
            String simpleAttributeEnumDataType = simpleAttribute.getEnumDataType();
            if (!StringUtils.isEmpty(simpleAttributeEnumDataType)) {
                if (!result.containsVertex(new MetaVertex(simpleAttributeEnumDataType, MetaType.ENUM))) {

                    MetaVertex metaVertex =
                            new MetaVertex.MetaVertexBuilder()
                                    .id(simpleAttributeEnumDataType)
                                    .displayName(simpleAttributeEnumDataType)
                                    .type(MetaType.ENUM)
                                    .status(MetaExistence.NOT_FOUND)
                                    .action(MetaAction.NONE)
                                    .messages(getNotFoundErrorMessage(simpleAttributeEnumDataType, MetaType.ENUM))
                                    .build();

                    result.addVertex(metaVertex);
                }
                MetaVertex targetVertex = new MetaVertex(simpleAttributeEnumDataType, MetaType.ENUM);
                if (!result.containsEdge(sourceVertex, targetVertex)) {
                    result.addEdge(sourceVertex, targetVertex);
                }
            }
        }
    }

    private void processSimpleLookupAttribute(MetaGraph result, MetaVertex sourceVertex,
                                              Set<MetaType> typesToProcess, String lookupEntityType) {

        if (!typesToProcess.contains(MetaType.LOOKUP) || StringUtils.isEmpty(lookupEntityType)) {
            return;
        }

        if (!result.containsVertex(new MetaVertex(lookupEntityType, MetaType.LOOKUP))) {
            result.addVertex(
                    new MetaVertex.MetaVertexBuilder()
                            .id(lookupEntityType)
                            .displayName(lookupEntityType)
                            .type(MetaType.LOOKUP)
                            .status(MetaExistence.NOT_FOUND)
                            .action(MetaAction.NONE)
                            .messages(getNotFoundErrorMessage(lookupEntityType, MetaType.LOOKUP))
                            .build()
            );
        }
        MetaVertex targetVertex = new MetaVertex(lookupEntityType, MetaType.LOOKUP);
        if (!result.containsEdge(sourceVertex, targetVertex)) {
            result.addEdge(sourceVertex, targetVertex);
        }
    }

    /**
     * Process A as.
     *
     * @param result the result
     * @param sourceVertex the source vertex
     * @param aas the aas
     * @param typesToProcess the types to process
     */
    private void processAAs(MetaGraph result, MetaVertex sourceVertex, List<ArrayMetaModelAttribute> aas,
                            Set<MetaType> typesToProcess) {
        if (aas == null) {
            return;
        }
        for (ArrayMetaModelAttribute aa : aas) {

            processSimpleLookupAttribute(result, sourceVertex, typesToProcess, aa.getLookupEntityType());
        }
    }


    /**
     * Check duplicates.
     *
     * @param metaGraph the meta graph
     */
    private void checkDuplicates(MetaGraph metaGraph) {

        Set<MetaVertex> vertexes = metaGraph.vertexSet();

        if (CollectionUtils.isEmpty(vertexes)) {
            return;
        }


        Map<String, List<MetaVertex>> nameMap = vertexes.stream()
                .filter(Objects::nonNull)
                .map(v -> {
                    if (v.getType() == MetaType.CUSTOM_CF && StringUtils.contains(v.getId(), PATH_DELIMETER)) {
                        v.setStatus(MetaExistence.EXIST);
                        v.setAction(MetaAction.NONE);
                    }
                    return v;
                })
                .collect(
                        Collectors.groupingBy(MetaVertex::getId)
                );

        nameMap.values()
                .forEach(v -> {
                    // mark elements as duplicates
                    Optional<MetaVertex> alreadyExist = v.stream()
                            .filter(el -> el.getStatus() == MetaExistence.EXIST)
                            .findAny();

                    v.stream()
                            .filter(el -> el.getStatus() != MetaExistence.EXIST && alreadyExist.isPresent())
                            .forEach(el ->
                                    el.getMessages()
                                            .add(
                                                    createErrorMessage(
                                                            TextUtils.getText(
                                                                    MetaExceptionIds.EX_META_IMPORT_MODEL_EL_DUPLICATE.code(),
                                                                    el.getId(), el.getType(), alreadyExist.get().getDisplayName(),
                                                                    alreadyExist.get().getType()))
                                            ));
                });

    }

    /**
     * Process lookup entities.
     *
     * @param les the les
     * @param result the result
     * @param typesToProcess the types to process
     */
    private void processLookupEntities(List<LookupEntity> les, MetaGraph result, Set<MetaType> typesToProcess) {
        for (LookupEntity le : les) {

            MetaVertex eVertex = new MetaVertex(le.getName(), MetaType.LOOKUP);

            List<SimpleMetaModelAttribute> sas = le.getSimpleAttribute();
            processSimpleAttributes(result, eVertex, sas, typesToProcess);
            List<ArrayMetaModelAttribute> aas = le.getArrayAttribute();
            processAAs(result, eVertex, aas, typesToProcess);
            MergeSettings mergeSettings = le.getMergeSettings();

            if (typesToProcess.contains(MetaType.SOURCE_SYSTEM)) {
                processMergeSettings(result, eVertex, mergeSettings);
            }

            if (typesToProcess.contains(MetaType.GROUPS)) {
                processGroupName(result, eVertex, le.getGroupName());
            }
        }
    }

    /**
     * Process group name.
     *
     * @param result the result
     * @param eVertex the e vertex
     * @param groupName the group name
     */
    private void processGroupName(MetaGraph result, MetaVertex eVertex, String groupName) {
        if (!result.containsVertex(new MetaVertex(groupName, MetaType.GROUPS))) {
            result.addVertex(
                    new MetaVertex.MetaVertexBuilder()
                            .id(groupName)
                            .displayName(groupName)
                            .type(MetaType.GROUPS)
                            .status(MetaExistence.NOT_FOUND)
                            .action(MetaAction.NONE)
                            .messages(getNotFoundErrorMessage(groupName, MetaType.GROUPS))
                            .build()

            );

        }
        result.addEdge(eVertex, new MetaVertex(groupName, MetaType.GROUPS));

    }


    /**
     * Adds the vertex.
     *
     * @param result the result
     * @param vertex the vertex
     */
    private void addVertex(MetaGraph result, MetaVertex vertex) {

        if (!result.containsVertex(vertex)) {
            result.addVertex(vertex);
            return;
        }

        // update already existed vertex
        // not optimal
        Optional<MetaVertex> first = result.vertexSet()
                .stream()
                .filter(v -> v.equals(vertex))
                .findFirst();

        if (first.isEmpty()) {
            return;
        }

        MetaVertex toModify = first.get();

        if (vertex.getType() != toModify.getType()
                && toModify.getStatus() != MetaExistence.NOT_FOUND
        ) {
            vertex.getMessages()
                    .add(
                            createErrorMessage(
                                    TextUtils.getText(
                                            MetaExceptionIds.EX_META_IMPORT_MODEL_EL_DUPLICATE.code(),
                                            vertex.getId(),
                                            vertex.getType(),
                                            toModify.getDisplayName(),
                                            toModify.getType()))
                    );
        }

        toModify.setAction(vertex.getAction());
        toModify.setMessages(vertex.getMessages());

        MetaExistence status =
                (toModify.getStatus() == MetaExistence.EXIST || toModify.getStatus() == MetaExistence.UPDATE)
                        ? MetaExistence.UPDATE
                        : vertex.getStatus();

        toModify.setStatus(status);
        toModify.setDisplayName(vertex.getDisplayName());

    }

    private static MetaMessage createErrorMessage(String message) {
        return new MetaMessage().withMessage(message).withStatus(MetaStatus.ERROR);
    }

    private MetaAction calculatingActionFromStatus(MetaExistence defaultStatus) {
        return defaultStatus == MetaExistence.NEW ? MetaAction.UPSERT : MetaAction.NONE;
    }

}

